package org.sosie2.service.authentication;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.InvalidPropertiesFormatException;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;

import org.apache.axis.Message;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSUsernameTokenPrincipal;
import org.sosie2.service.framework.AbstractService;
import org.sosie2.service.framework.SAMLAnnotation;
import org.sosie2.service.framework.SosieException;
import org.sosie2.service.framework.Utils;
import org.w3c.dom.Document;

/**
 * 
 * Examplifies a prototype for a web service that is handled requests from clients
 * and using the security header attached, verifies the identity of the requestor.
 * The authentication is based on a simple properties-file that contains several
 * pairs of usernames-passwords.
 * 
 * @author Gabriela
 */
public class SimpleAuthenticationService extends AbstractService
{
	public static Logger logger 			= Logger.getLogger(LDAPAuthenticationService.class);
	
	private static final String ACTOR  		= Utils.AUTH_ACTOR;
	private static final String AUTHOR 		= Utils.AUTH_AUTHOR;
	
	private SimpleCallbackHandler callback 	= null;
	
	protected String authenticatedUser 		= null;
	
	/**
	 * The function initializes the logger config file, and the callback handler which will be 
	 * used when checking user passwords 
	 */
	public void init() {
		Utils instanceUtils = Utils.get();
		URL url = instanceUtils.getFile("configuration//" + Utils.LOGGER_FILE);
		if (url == null)
			System.out.println("---------------------URL NULL" );
		else 	
			PropertyConfigurator.configure(url);
		
		logger.info("Authentication service started ...");
		
		
		ProtectionDomain domain = this.getClass().getProtectionDomain();
	    
	    // With the protection domain, get all the permissions from the Policy object
	    PermissionCollection pcoll = Policy.getPolicy().getPermissions(domain);
	    
	    // View each permission in the permission collection
	    Enumeration enume = pcoll.elements();
	    while (enume.hasMoreElements() ) {
	        Permission p = (Permission)enume.nextElement();
	        logger.info("Permission: user="+p.getName()+" can="+p.getActions());
	    }
		
		try {
			Class c = Thread.currentThread().getContextClassLoader().loadClass("org.sosie2.service.authentication.SimpleCallbackHandler");
			callback = (SimpleCallbackHandler) c.newInstance();
			Properties users = this.loadProperties(Utils.AUTHENTICATION_FILE);		
			callback.init(users); 
		} catch(Exception ex) {
			logger.fatal("**************Exception in Authentication Service");
			ex.printStackTrace();
		}
	}
	
	/**
	 * Simple constructor
	 */
	public SimpleAuthenticationService() {
		init();
	}
	
	/**
	 * This is the main method of the web service. It parses the header of the soap message
	 * and after getting the username, it passes it to the callback handler
	 * to check it against the given password;
	 * the callback uses calls to a method that interrogates a LDAP server 
	 */
	public Message invoke(Message req, Message resp) throws Exception {
		// initialization of logger and callback handler		
		logger.info("started");
		
		// verify the username and password with the callback from wss4j 
		WSSecurityEngine secEngine = WSSecurityEngine.getInstance();		
		Vector results = secEngine.processSecurityHeader(req.getSOAPEnvelope().getAsDocument(),null,callback, null );
		
		if (results == null) {
			logger.info("AUTHENTICATION SERVICE--------------------------");				
			throw new SosieException("The method 'processSecurityHeaders' returns smth null! ...");
		} else if (results.size() == 1)  {
			WSSecurityEngineResult eResult = (WSSecurityEngineResult)results.get(0);
			WSUsernameTokenPrincipal usernameTokenPrincipal = (WSUsernameTokenPrincipal)eResult.getPrincipal();
			
			String wannabeName = usernameTokenPrincipal.getName();
    		
   			logger.info("AUTHENTICATION SERVICE--------------------------");
			logger.info("User " + wannabeName + "is authenticated !");
			this.authenticatedUser = wannabeName;
			
			// the response sent to the client, is composed of its original message + an annotation;
			// first, copy the headers of the request message, into the response message
    		SOAPHeader header = req.getSOAPHeader();
    		this.copyHeader(header, resp.getSOAPHeader());
    		
    		Document doc = resp.getSOAPEnvelope().getAsDocument();

    		// create attribute-values array
    		String[][] att = new String[1][2];
    		att[0][0] = "authenticatedUser";
    		att[0][1] = wannabeName;
    		
    		//attach the hashmap + rest of parameters to the annotation
    		SAMLAnnotation sa = new SAMLAnnotation (att, ACTOR, "LDAPAuthentication");
    		doc = addAnnotation(doc, sa, AUTHOR);
    		        		        		
    		SOAPMessage soapMsg = Utils.toMessage(doc);
    		ByteArrayOutputStream out = new ByteArrayOutputStream();
    		soapMsg.writeTo(out);
    		
    		Message msg = new Message(new ByteArrayInputStream(out.toByteArray()), false);
    		return msg;
		} else if (results.size() == 0) {
			logger.info("Invalid user" );
			throw new SosieException("Authentication Failed. Invalid User!");			
		}
		
		throw new SosieException("Should never reach this point.");
    }
    	
	
	/**
	 * Method for loading the properties needed for a custom configuration of the mail alerting service.
	 * @param propertyFile
	 * @return a Properties Object
	 * @throws FileNotFoundException
	 * @throws InvalidPropertiesFormatException
	 * @throws IOException
	 */
	protected Properties loadProperties(String propertyFile) throws Exception {	
		Properties defaultProps = null;
		
		try {
			defaultProps = new Properties();
			FileInputStream in = new FileInputStream(propertyFile);
			defaultProps.load(in);
			in.close();
		}
		catch(Exception e)
		{
			logger.warn("CheckForAlertService: exception while loading properties");
			e.printStackTrace();
		}

		return defaultProps;
	
	} 
}
